﻿// ----------------------------------------------------------------------
// Sources of MetaLinq Project at http://metalinq.codeplex.com/
// (c) 2007-2009 by Aaron Erickson and idof
// (c) 2009 of changes to original sources by Thomas Scheller
// 
// Express written permission from Aaron Erickson exists to include these 
// sources into the Xcoordination Application Space. Thanks Aaron!
// ----------------------------------------------------------------------

using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.Reflection;

namespace ExpressionBuilder
{
    [DataContract]
	[Serializable]
	[KnownType(typeof(EditableMemberExpression))]
    [KnownType(typeof(EditableListInitExpression))]
    [KnownType(typeof(EditableNewExpression))]
    [KnownType(typeof(EditableNewArrayExpression))]
    [KnownType(typeof(EditableTypeBinaryExpression))]
    [KnownType(typeof(EditableMemberInitExpression))]
    [KnownType(typeof(EditableInvocationExpression))]
    [KnownType(typeof(EditableBinaryExpression))]
    [KnownType(typeof(EditableParameterExpression))]
    [KnownType(typeof(EditableExpressionCollection))]
    [KnownType(typeof(EditableConstantExpression))]
    [KnownType(typeof(EditableConditionalExpression))]
    [KnownType(typeof(EditableUnaryExpression))]
    [KnownType(typeof(EditableMethodCallExpression))]
    [KnownType(typeof(EditableMemberAssignment))]
    [XmlInclude(typeof(EditableMemberExpression))]
    [XmlInclude(typeof(EditableListInitExpression))]
    [XmlInclude(typeof(EditableNewExpression))]
    [XmlInclude(typeof(EditableNewArrayExpression))]
    [XmlInclude(typeof(EditableTypeBinaryExpression))]
    [XmlInclude(typeof(EditableMemberInitExpression))]
    [XmlInclude(typeof(EditableInvocationExpression))]
    [XmlInclude(typeof(EditableBinaryExpression))]
    [XmlInclude(typeof(EditableParameterExpression))]
    [XmlInclude(typeof(EditableExpressionCollection))]
    [XmlInclude(typeof(EditableConstantExpression))]
    [XmlInclude(typeof(EditableConditionalExpression))]
    [XmlInclude(typeof(EditableUnaryExpression))]
    [XmlInclude(typeof(EditableMethodCallExpression))]    
    [XmlInclude(typeof(EditableMemberAssignment))]
    public abstract class EditableExpression
    {        
        // Properties
        [DataMember]        
        public abstract ExpressionType NodeType { get; set; }

        [XmlIgnore]
        public Type Type
        {
            get;
			protected set;
        }
        [DataMember]
        public string TypeName
        {
            get
            {                
                if (Type == null)
                    return null;

                return Type.ToSerializableForm();
            }
            set
            {
                if (value != null)
                    Type = Type.FromSerializableForm(value);
            }
        }

        // Ctors
        public EditableExpression() { } //allow for non parameterized creation for all expressions

        public EditableExpression(Type type) 
        {
            Type = type;
        }

        // Methods
        public static EditableExpression CreateEditableExpression<TResult>(Expression<Func<TResult>> ex)
        {
            LambdaExpression lambEx = Expression.Lambda<Func<TResult>>(ex.Body, ex.Parameters);
            return new EditableLambdaExpression(lambEx);
        }

        public static EditableExpression CreateEditableExpression<TArg0, TResult>(Expression<Func<TArg0, TResult>> ex)
        {
            LambdaExpression lambEx = Expression.Lambda<Func<TArg0, TResult>>(ex.Body, ex.Parameters);
            return new EditableLambdaExpression(lambEx);
        }


        public static EditableExpression CreateEditableExpression<TArg0, TArg1, TResult>(Expression<Func<TArg0, TArg1, TResult>> ex)
        {
            LambdaExpression lambEx = Expression.Lambda<Func<TArg0, TArg1, TResult>>(ex.Body, ex.Parameters);
            return new EditableLambdaExpression(lambEx);
        }


        public static EditableExpression CreateEditableExpression<TArg0, TArg1, TArg2, TResult>(Expression<Func<TArg0, TArg1, TArg2, TResult>> ex)
        {
            LambdaExpression lambEx = Expression.Lambda<Func<TArg0, TArg1, TArg2, TResult>>(ex.Body, ex.Parameters);
            return new EditableLambdaExpression(lambEx);
        }


        public static EditableExpression CreateEditableExpression<TArg0, TArg1, TArg2, TArg3, TResult>(Expression<Func<TArg0, TArg1, TArg2, TArg3, TResult>> ex)
        {
            LambdaExpression lambEx = Expression.Lambda<Func<TArg0, TArg1, TArg2, TArg3, TResult>>(ex.Body, ex.Parameters);
            return new EditableLambdaExpression(lambEx);
        }

        public static EditableExpression CreateEditableExpression(Expression ex)
        {
			//Changes in here were made to transform MemberExpressions into ConstantExpressions! (because MemberExpression tend to be not serializable)

			if (ex is ConstantExpression) return new EditableConstantExpression(ex as ConstantExpression);
			else if (ex is BinaryExpression) return new EditableBinaryExpression(ex as BinaryExpression);
			else if (ex is ConditionalExpression) return new EditableConditionalExpression(ex as ConditionalExpression);
			else if (ex is InvocationExpression) return new EditableInvocationExpression(ex as InvocationExpression);
			else if (ex is LambdaExpression) return new EditableLambdaExpression(ex as LambdaExpression);
			else if (ex is ParameterExpression) return new EditableParameterExpression(ex as ParameterExpression);
			else if (ex is ListInitExpression) return new EditableListInitExpression(ex as ListInitExpression);
			else if (ex is MemberExpression) return GetConstantFromMemberExpression(ex as MemberExpression); //EditableMemberExpression(ex as MemberExpression);
			else if (ex is MemberInitExpression) return new EditableMemberInitExpression(ex as MemberInitExpression);
			else if (ex is MethodCallExpression) /*throw new Exception("No method calls allowed in predicate!");*/ return new EditableMethodCallExpression(ex as MethodCallExpression);
			else if (ex is NewArrayExpression) return new EditableNewArrayExpression(ex as NewArrayExpression);
			else if (ex is NewExpression) return new EditableNewExpression(ex as NewExpression);
			else if (ex is TypeBinaryExpression) return new EditableTypeBinaryExpression(ex as TypeBinaryExpression);
			else if (ex is UnaryExpression) return new EditableUnaryExpression(ex as UnaryExpression);
			else return null;
        }

		private static EditableExpression GetConstantFromMemberExpression(MemberExpression ex)
		{
			//Transforms the MemberExpression into a constant one, because MemberExpressions cannot be serialized!!
			if (ex.Expression is ConstantExpression)
			{
				ConstantExpression constExpr = ex.Expression as ConstantExpression;
				Type constType = constExpr.Value.GetType();
				FieldInfo field = constType.GetField(ex.Member.Name);
				object value = field.GetValue(constExpr.Value);
				return new EditableConstantExpression(value);
			}
			else if (ex.Expression is MemberExpression)
			{
				//MemberExpression contains MemberExpression: happens when Fields are retrieved with deeper hierarchy, e.g. obj1.obj2.value
				EditableExpression innerExpr = GetConstantFromMemberExpression(ex.Expression as MemberExpression);
				if (innerExpr is EditableConstantExpression)
				{
					EditableConstantExpression constExpr = (EditableConstantExpression)innerExpr;
					Type constType = constExpr.Value.GetType();
					object value = null;
					if (ex.Member is FieldInfo)
					{
						value = (ex.Member as FieldInfo).GetValue(constExpr.Value);
					}
					else if (ex.Member is PropertyInfo)
					{
						value = (ex.Member as PropertyInfo).GetValue(constExpr.Value, null);
					}
					else
						throw new Exception("Instance Field/Property not recognized!!");
					return new EditableConstantExpression(value);
				}
				else
					return new EditableMemberExpression(ex);
			}
			else if (ex.Expression is ParameterExpression)
			{
				//when MemberExpression refers to an expression parameter, it is valid and must not be converted
				return new EditableMemberExpression(ex);
			}
			else if (ex.Expression == null)
			{
				//Expression is null when the accessed field or property is static
				object value = null;
				if (ex.Member is FieldInfo)
				{
					value = (ex.Member as FieldInfo).GetValue(null);
				}
				else if (ex.Member is PropertyInfo)
				{
					value = (ex.Member as PropertyInfo).GetValue(null, null);
				}
				else
					throw new Exception("Static Field/Property not recognized!!");
				return new EditableConstantExpression(value);
			}
			else
			{
				throw new Exception("MemberExpression containing " + ex.Expression.GetType() + " is not Serializable!");
			}
		}

		public abstract Expression ToExpression();
    }
}
